package com.spukmk3me.util;

import com.spukmk3me.math.FPMath;

public class ContinuousTimedAnimation
{
    public ContinuousTimedAnimation() {}
    
    public void execute( int deltaTime )
    {
        if ( !m_animating )
            return;
        
        int consumedTime, stepTimeLeft;

        while ( deltaTime > 0 )
        {
            //System.out.println( m_remainingTime );
            
            if ( m_direction )
                stepTimeLeft  = getBlockTime( getLeftIndex() ) - m_blockTimeFactor;
            else
                stepTimeLeft  = m_blockTimeFactor;
            
            if ( m_remainingTime == -1 ) // infinite remaining time
                consumedTime    = Math.min( stepTimeLeft, deltaTime );
            else
            {
                consumedTime        = Math.min( Math.min( stepTimeLeft, m_remainingTime ), deltaTime );
                m_remainingTime    -= consumedTime;
            }
            
            deltaTime -= consumedTime;
            
            if ( m_direction )
            {
                m_blockTimeFactor += consumedTime;
                
                if ( m_blockTimeFactor == getBlockTime( m_currentIndex ) )
                {
                    m_currentIndex = getRightIndex();
                    m_blockTimeFactor = 0;
                }
            }
            else
            {
                m_blockTimeFactor -= consumedTime;
                
                if ( m_blockTimeFactor == 0 )
                {
                    m_currentIndex = getLeftIndex();
                    m_blockTimeFactor = 0;
                }
            }

            if ( m_remainingTime == 0 )
            {
                stopAnimation();
                break;
            }
        }
    }
    
    public void setCurrentIndex( int index )
    {
        //if ( !m_animating )
        {
            m_currentIndex = index >> 16;
            m_blockTimeFactor = FPMath.fpMul( index & 0x0000FFFF, getBlockTime( m_currentIndex ) );
        }
    }
    
    public int getCurrentIndex()
    {
        return (m_currentIndex << 16) + FPMath.fpDiv( m_blockTimeFactor, getBlockTime( m_currentIndex ) );
    }
    
    public int getLeftIndex()
    {
        if ( m_blockTimeFactor != 0 )
            return m_currentIndex;
        else
        {
            if ( m_direction )
                return m_currentIndex;
            else
            {
                if ( m_currentIndex == m_firstIndex )
                    return m_lastIndex;
                else
                    return m_currentIndex - 1;
            }
        }
    }
    
    public int getRightIndex()
    {
        if ( m_blockTimeFactor != 0 )
        {
            if ( m_currentIndex == m_lastIndex )
                return m_firstIndex;
            else
                return m_currentIndex + 1;
        }
        else
        {
            if ( m_direction )
            {
                if ( m_currentIndex == m_lastIndex )
                    return m_firstIndex;
                else
                    return m_currentIndex + 1;
            }
            else
                return m_currentIndex;
        }
    }
    
    public boolean isAnimating()
    {
        return m_animating;
    }
    
    /**
     *   firstIndex         lastIndex
     *        |<--active range->|
     *  0     1     2     3     4                n-1   n==0
     *  |-----|-----|-----|-----|-----|-----|-----|-----|
     *   time0 time1 time2 time3                   t n-1
     * @param mode
     * @param firstIndex
     * @param lastIndex
     * @param times
     * @param time
     * @param limitParam
     * @param fixedStopPosition
     */
    public void setAnimation( int mode, int firstIndex, int lastIndex,
        int[] times, int time, int limitParam )
    {
        m_animating         = (mode & MODE_ANIMATING) != 0;
        
        if ( m_animating )
        {
            m_mode              = mode;
            m_firstIndex        = firstIndex;
            m_lastIndex         = lastIndex;
            m_blockTimes        = times;
            m_blockTime         = time;
            m_direction         = (mode & MODE_BACKWARD) == 0;
            m_fixedStop         = (mode & MODE_USEFIXEDSTOP) != 0;
            
            if ( (mode & MODE_TIMESTOP) != 0 ) //limitParam = time to stop (fixed 16.16)
                m_remainingTime = limitParam;
            else if ( (mode & MODE_BLOCKSTOP) != 0 ) //limitParam = number of blocks to stop (fixed 16.16)
                calculateRemainingTimeBasedOnBlocks( limitParam );
            else
                m_remainingTime = -1; // infinite
        }
    }
    
    private void calculateRemainingTimeBasedOnBlocks( int nBlock )
    {
        int leftBorderIndex, rightBorderIndex, nRound;
        int currentIndex = getCurrentIndex();
        
        nBlock <<= 16;
        
        if ( m_direction )
        {
            leftBorderIndex     = currentIndex;
            rightBorderIndex    =
                (currentIndex - (m_firstIndex << 16) + nBlock) %
                (m_lastIndex - m_firstIndex + 1 << 16) + (m_firstIndex << 16);
            nRound              =
                (currentIndex - (m_firstIndex << 16) + nBlock) /
                (m_lastIndex - m_firstIndex + 1 << 16);
        }
        else
        {
            rightBorderIndex    = currentIndex;
            leftBorderIndex     = (m_lastIndex << 16) -
                ((m_lastIndex << 16) - currentIndex + nBlock) %
                (m_lastIndex - m_firstIndex + 1 << 16);
            nRound              =
                ((m_lastIndex << 16) - currentIndex + nBlock) /
                (m_lastIndex - m_firstIndex + 1 << 16);
        }
        
        System.out.println( "Round: " + nRound +
             " left: " + Integer.toHexString( leftBorderIndex ) +
             " right: " + Integer.toHexString( rightBorderIndex ) );
        
        if ( nRound == 0 )
            m_remainingTime = getTimeRange( leftBorderIndex, rightBorderIndex ); 
        else
        {
            m_remainingTime =
                getTimeRange( leftBorderIndex, m_lastIndex ) +
                nRound * getTimeRange( m_firstIndex, m_lastIndex ) +
                getTimeRange( m_firstIndex, rightBorderIndex );
        }
        
        System.out.println( "Remain time: " + Integer.toHexString( m_remainingTime ) );

    }
    
    private void stopAnimation()
    {
        m_animating = false;

        if ( m_fixedStop )
            m_currentIndex = LASTINDEX_TOKEN;
    }
    
    /**
     * 
     * @param frameIndex1 Fixed 16.16
     * @param frameIndex2 Fixed 16.16
     * @return Time to move from frameIndex1 (lower) to frameIndex2 (higher) without firstIndex/lastIndex restriction.
     */
    private int getTimeRange( int frameIndex1, int frameIndex2 )
    {
        int time = 0;
        int nextFrameIndex;
        
        System.out.println( Integer.toHexString( frameIndex1 ) + " " + Integer.toHexString( frameIndex2 ) );
        
        while ( frameIndex1 < frameIndex2 )
        {
            nextFrameIndex = Math.min( (frameIndex1 >> 16) + 1 << 16, frameIndex2 );
            time += FPMath.fpMul( getBlockTime( nextFrameIndex >> 16 ), nextFrameIndex - frameIndex1 );
            frameIndex1 = nextFrameIndex;
        }
        
        //for ( int i = 0; i != m_blockTimes.length; ++i )
        //    System.out.println( Integer.toHexString( m_blockTimes[ i ] ) );
        
        return time;
    }
    
    private int getBlockTime( int blockIndex )
    {
        if ( m_blockTimes == null )
            return m_blockTime;
        else
            return m_blockTimes[ blockIndex ];
    }
    
    public static final int MODE_ANIMATING      = 0x00000001;
    public static final int MODE_LOOP           = 0x00000002;
    public static final int MODE_BOUNCE         = 0x00000004;
    public static final int MODE_BACKWARD       = 0x00010000;
    public static final int MODE_TIMESTOP       = 0x00020000;
    public static final int MODE_BLOCKSTOP      = 0x00040000;
    public static final int MODE_USEFIXEDSTOP   = 0x00080000;

    /**
     *  Pass this value to SetAnimation() to get a infinite animation.
     */
    public static final int TIME_INFINITE       = -1;
    
    public static final int LASTINDEX_TOKEN = -1;
    
    private int[]               m_blockTimes;
    private int                 m_blockTime;
    private int                 m_currentIndex; // integer
    private int                 m_blockTimeFactor; // fixed 16.16
    private int                 m_mode, m_remainingTime;
    private int                 m_firstIndex, m_lastIndex;
    private boolean             m_direction, m_fixedStop, m_animating;
}
